'''
Created on 07.05.2012

@author: myaut
'''

from salsa.ast import ASTNode
from collections import OrderedDict

class ASTAnalyzerError(Exception):
    pass

class ASTContext(dict):
    class Tracker:
        def __init__(self):
            self.track = []
            self.is_committed = False
        
        def add(self, node):
            if self.is_committed:
                self.track.insert(0, node)
        
        def commit(self):
            self.is_committed = True
            
        def get(self):
            return self.track
    
    def __init__(self):
        self.trackers = {}
        self.analyzer = None
    
    def set_analyzer(self, an):
        self.analyzer = an
    
    def get_analyzer(self):
        if self.analyzer is None:
            raise ASTAnalyzerError('Analyzer is not found in context')
        
        return self.analyzer
    
    def start_tracking(self, tag, node):
        '''Tracking is a concept to track path from node A to node C:
        A -> B -> ... -> C when we don't know how path looks
        
        before(A) handler calls start_tracking(self)
        before(C) calls commit_tracking
        
        after(X) for each node add themselves to track's stack
        
        after(A) reads track using end_tracking(self)
        
        @note Only one tracker is allowed'''
        self.trackers[tag] = ASTContext.Tracker()
    
    def commit_tracking(self, tag):
        if tag in self.trackers:
            self.trackers[tag].commit()
        
    def end_tracking(self, tag):
        track = self.trackers[tag].get()
        del self.trackers[tag]
        
        return track
    
    def track_node(self, tag, node):
        for tracker in self.trackers.values():
            tracker.add((tag, node))

class ASTAnalyzer:
    BEFORE = 0
    AFTER = 1
    BOTH = 2
    AFTER_CHILD = 3
    
    def __init__(self, filter = None):       
        self.before_handlers = OrderedDict()
        self.after_handlers = OrderedDict()
        self.after_child_handlers = OrderedDict()
        
        self.auto_register()
    
    def register_handler(self, when, pred, handler):
        if when in (ASTAnalyzer.BEFORE, ASTAnalyzer.BOTH):
            self.before_handlers[pred] = handler
            
        if when in (ASTAnalyzer.AFTER, ASTAnalyzer.BOTH):
            self.after_handlers[pred] = handler
        
        if when == ASTAnalyzer.AFTER_CHILD:
            self.after_child_handlers[pred] = handler
    
    def register_putvar(self, pred, varname, varvalue):
        def put_var(node, context):
            context[varname] = varvalue
            
        def free_var(node, context):
            del context[varname]
            
        self.register_handler(ASTAnalyzer.BEFORE, pred, put_var)
        self.register_handler(ASTAnalyzer.AFTER, pred, free_var)
    
    def auto_register(self):
        handlers = [item 
                    for item 
                    in self.__class__.__dict__.values() 
                    if 'hid' in dir(item)]
        
        handlers = sorted(handlers, key = lambda h: h.hid)
        
        for handler in handlers:
            self.register_handler(handler.when, handler.pred, handler)
    
    @staticmethod
    def name_pred(name, and_pred = lambda node, ctx: True):
        return lambda node, ctx: node.name == name and and_pred(node, ctx)

class ASTAnalyzerNode(ASTNode):
    def _process_events(self, handlers, context, *args):
        for (pred, handler) in handlers.items():
            if pred(self, context):
                # print 'Calling %s for %s' % (handler, self)
                handler(self, context, *args)
                
    def before_children(self, context):
        self._process_events(context.get_analyzer().before_handlers, context)
    
    def after_children(self, context):
        self._process_events(context.get_analyzer().after_handlers, context)
        
    def after_child(self, ctag, cnode, context):
        context.track_node(ctag, cnode)
        
        self._process_events(context.get_analyzer().after_child_handlers, context, ctag, cnode)

handlerid = 0
        
def analyzer(when, pred):
    def wrap(func):
        global handlerid
        
        handlerid += 1
        
        f = func
        
        f.when = when
        f.pred = pred
        f.hid = handlerid
        
        return f
        
    return wrap